import { Container } from '../Container';
import { HelperTool } from './Common';
import { DataService } from "./DataService";
import { ModelManService } from './ModelManService';
import { ViewerService } from "./ViewerService";
export class GroupManService {
    enableThemeColor = false;
    constructor(private viewerService = Container.defineProperty(ViewerService),
        private dataService = Container.defineProperty(DataService),
        private modelManService = Container.defineProperty(ModelManService)) {
    }
    Init() {
        this.modelManService.UpdateChanged.addEventListener(async () => {
            await this.checkDbIds();
            await this.updateViewer();
        });

    }
    getData(): { groups: GroupEntity[]; } {
        if (!this.dataService.Data.groups) {
            this.dataService.Data.groups = [];
        }
        return this.dataService.Data;
    }
    getModelName(model: Sippreep.Viewing.Model) {
        return this.modelManService.getModelInfo(model).name;
    }
    async updateViewer() {
        let groups = this.getData().groups;
        let temp = this.viewerService.viewer.getAllModels().map(async (model) => {
            let idMan = this.getMan(ExternalIdMan, model);
            await idMan.InitPromse;
            groups.forEach(item => {
                item.selection.forEach(s => {
                    if (s.name == this.getModelName(model)) {
                        if (!s.ids)
                            s.ids = idMan.toExternalId(s.dbIds);
                    }
                });
            });

            {
                let materialColorMan = this.getMan(MaterialColor, model);
                await materialColorMan.InitPromse;
                materialColorMan.clearColor();
                if (!this.enableThemeColor) {
                    groups.forEach((item) => {
                        let disableColor = item.disableColor == undefined ? false : item.disableColor;
                        if (!disableColor) {
                            item.selection.forEach(s => {
                                if (s.name == this.getModelName(model)) {
                                    materialColorMan.setColor(s.dbIds, item.color);
                                }
                            });
                        }
                    });
                }
            }

            {
                let materialColorMan = this.getMan(ThemeColorMan, model);
                await materialColorMan.InitPromse;
                materialColorMan.clearColor();
                if (this.enableThemeColor) {
                    groups.forEach((item) => {
                        let disableColor = item.disableColor == undefined ? false : item.disableColor;
                        if (!disableColor) {
                            item.selection.forEach(s => {
                                if (s.name == this.getModelName(model)) {
                                    materialColorMan.setColor(s.dbIds, item.color);
                                }
                            });
                        }
                    });
                }
            }

            let transformMan = this.getMan(TransformMan, model);
            await transformMan.InitPromse;
            transformMan.clear();
            groups.forEach((item) => {
                item.selection.forEach(s => {
                    if (s.name == this.getModelName(model)) {
                        //transformMan.setTransform(s.dbIds, item.transform!);

                        let parents = this.findParentByTransform(item, groups);
                        parents.unshift(item);
                        let trans = parents.map(_ => _.transform || { center: [0, 0, 0], position: [0, 0, 0], rotation: [0, 0, 0], axisAngle: [0, 0, 0, 0], scale: [1, 1, 1] });
                        transformMan.setTransforms(s.dbIds, trans);
                    }
                });
            });
        });
        await Promise.all(temp);
        this.viewerService.viewer.impl.sceneUpdated(true);
        //this.ViewerService.viewer.impl.invalidate(true, true, true);
    }
    getBox(value: GroupEntity) {
        const array = new Float32Array(6);
        const point = new THREE.Vector3();
        const box = new THREE.Box3();
        this.viewerService.viewer.getAllModels().forEach(m => {
            value.selection.forEach(_ => {
                if (_.name == this.getModelName(m)) {
                    let t = m.getInstanceTree();
                    _.dbIds.forEach(_ => {
                        t.getNodeBox(_, array);
                        point.fromArray(array as any);
                        box.expandByPoint(point);
                        point.fromArray(array as any, 3);
                        box.expandByPoint(point);
                    });
                }
            });
        });
        return box;
        //window.alert(JSON.stringify({ center: box.center(), size: box.size() }));
    }
    getWorldTransformByGroup(data: GroupEntity) {
        let parents = this.findParentByTransform(data, this.getData().groups);
        parents.unshift(data);
        let trans = parents.map(_ => _.transform || this.transformDefault());
        return this.getWorldTransforms(trans)
    }
    setWorldTransformByGroup(data: GroupEntity, object3D: THREE.Object3D) {
        if (!data.transform)
            data.transform = this.transformDefault();
        data.transform.center = object3D.parent.getWorldPosition().toArray();
        data.transform.position = object3D.position.toArray();
        data.transform.rotation = object3D.rotation.toArray().splice(0, 3).map(_ => _ * 180 * Math.PI);
        data.transform.scale = object3D.scale.toArray();
    }
    transformDefault(): GroupTransformEntity {
        return { center: [0, 0, 0], position: [0, 0, 0], rotation: [0, 0, 0], axisAngle: [0, 0, 0, 0], scale: [1, 1, 1] }
    }
    transformIsDefault(value: GroupTransformEntity) {
        if (value == undefined || value == null)
            return true;
    }
    private findParentByTransform(value: GroupEntity, groups: GroupEntity[], parents?: GroupEntity[]) {
        if (!parents)
            parents = [];
        if (value.transformParent) {
            let parent = groups.find(_ => _.name == value.transformParent!);
            if (parent) {
                parents.push(parent);
                this.findParentByTransform(parent, groups, parents);
            }
        }
        return parents;
    }
    private getWorldTransforms(tranforms: GroupTransformEntity[]) {
        let worldPoint = new THREE.Object3D();
        let currentPoint = worldPoint;
        let map = tranforms.map(tranform => {
            let movePoint = new THREE.Object3D();
            movePoint.position.fromArray(tranform.center);
            movePoint.updateMatrixWorld(true);

            currentPoint.position.copy(movePoint.worldToLocal(currentPoint.position));
            currentPoint.parent = movePoint;
            currentPoint = movePoint;

            return { tranform, movePoint };
        });
        map.forEach(({ tranform, movePoint }) => {

            movePoint.position.add(new THREE.Vector3().fromArray(tranform.position));

            if (tranform.rotation.length === 3) {
                movePoint.rotation.fromArray(tranform.rotation.map(_ => _ / 180 * Math.PI));
            } else if (tranform.rotation.length === 4) {
                // 当rotation数据长度为4时，取前三个值为旋转轴，第四个值为右手旋转角度数，（中心依旧为center）
                movePoint.rotation.setFromQuaternion(
                    new THREE.Quaternion().setFromAxisAngle(
                        new THREE.Vector3().fromArray(tranform.rotation.slice(0, 3)).normalize(),
                        tranform.rotation.at(-1)! / 180 * Math.PI,
                    ),
                );
            }

            movePoint.scale.fromArray(tranform.scale);
        });
        map.reverse().forEach(({ movePoint }) => {
            movePoint.updateMatrixWorld(true);
        });

        return worldPoint;
    }

    private async checkDbIds() {
        let groups = this.getData().groups;
        let temp = this.viewerService.viewer.getAllModels().map(async (model) => {
            let idMan = this.getMan(ExternalIdMan, model);
            await idMan.InitPromse;
            groups.forEach(item => {
                item.selection.forEach(s => {
                    if (s.name == this.getModelName(model)) {
                        if (s.ids) {
                            //如果存在无id的节点则不检查更新
                            if (s.ids.findIndex(_ => _ == null) < 0) {
                                s.dbIds = idMan.toDbId(s.ids);
                            }
                        }
                    }
                });
            });
        });
        await Promise.all(temp);
    }
    private getMan<T>(value: { new(model: Sippreep.Viewing.Model, viewer: Sippreep.Viewing.Viewer3D, service: GroupManService): T }, model: Sippreep.Viewing.Model | any) {
        if (!model[value.name]) {
            model[value.name] = new value(model, this.viewerService.viewer, this);
        }
        return model[value.name] as T;
    }
}
export type GroupEntity = {
    name: string,
    selection: { name: string, dbIds: number[], ids?: string[] }[]
    hide?: boolean,
    transparent?: boolean,
    color: Array<number>, disableColor?: boolean,
    transformParent?: string,
    transform?: GroupTransformEntity,
}
export type GroupTransformEntity = {
    center: number[]
    position: number[]
    rotation: number[]
    axisAngle: number[]
    scale: number[]
}
/**
 * 材质颜色管理
 *
 * 给材质上色，恢复材质颜色
 */
class MaterialColor {
    /**
     * 原有材质+新颜色
     */
    private _colorMats = new Map<string, THREE.Material>();
    /**
     * 记录原有材质
     */
    private _fragOldMat = new Map<number, THREE.Material>();
    private _oldMatKeys: string[] = null!;
    private _tree: Sippreep.Viewing.InstanceTree = null!;
    InitPromse: Promise<void> = null!;
    constructor(private model: Sippreep.Viewing.Model, private viewer: Sippreep.Viewing.Viewer3D) {
        this.InitPromse = this.initPromse();
    }
    private async initPromse() {
        this._tree = await new Promise<Sippreep.Viewing.InstanceTree>((s, f) => {
            this.model.getObjectTree((tree) => { s(tree) });
        });
        let fragList = this.model.getFragmentList();
        let maxFragId = fragList.fragments.fragId2dbId.length - 1
        await HelperTool.WaitTrue(() => {
            if (fragList.getMaterial(maxFragId))
                return true;
            return false;
        });
        this._oldMatKeys = Object.keys(this.getMatIdMaps());
        //@ts-ignore
        this.model.unconsolidate();
    }

    private findMat(mat: THREE.Material, color: number[]): THREE.Material {
        const key = mat.uuid + "|" + color.join(",");
        let newMat: THREE.Material | undefined = this._colorMats.get(key);
        if (!newMat) {
            newMat = this.viewer.impl.getMaterials().cloneMaterial(mat, this.model);
            (newMat as any).color = new THREE.Color().fromArray(HelperTool.ToThreeColor(color));
            this._colorMats.set(key, newMat!);
            this.viewer.impl.matman().addHDRMaterial(key, newMat);
        }
        return newMat!;
    }
    private getMatIdMaps() {
        //@ts-ignore
        return this.model.getFragmentList().materialIdMap;
    }
    private clearCatche() {
        if (this._colorMats.size == 0)
            return;

        let oldKeys = this._oldMatKeys;
        let map = this.getMatIdMaps();
        let temp = Object.keys(map);
        for (let i = oldKeys.length; i < temp.length; i++) {
            delete map[temp[i]];
        }

        // 从材质管理器中删除 自定义的颜色材质
        Array.from(this._colorMats.keys()).forEach(key => {
            this.viewer.impl.matman().removeMaterial(key);
        })
        this._colorMats.clear();
    }
    clearColor() {
        let fragList = this.model.getFragmentList();
        this._fragOldMat.forEach((v, k) => fragList.setMaterial(k, v));
        this._fragOldMat.clear();
        this.clearCatche();
    }
    setColor(dbIds: number[], color: number[]) {
        let fragIds = new Array<number>();
        let tree = this._tree;
        dbIds.forEach((dbId) => {
            tree.enumNodeFragments(dbId, (fragId: number) => {
                fragIds.push(fragId);
            }, true);
        });

        let fragList = this.model.getFragmentList();

        fragIds.forEach(fragId => {
            let oldMaterial = fragList.getMaterial(fragId);
            if (oldMaterial) {
                if (this._fragOldMat.has(fragId))
                    oldMaterial = this._fragOldMat.get(fragId) as THREE.Material;
                else
                    this._fragOldMat.set(fragId, oldMaterial);

                let colorMat = this.findMat(oldMaterial, color);
                fragList.setMaterial(fragId, colorMat);
                // @ts-ignore
                // console.log(["fragId has mat!",fragId,fragList.getMaterialId(fragId),this.model.id]);
            }
            else {
                //@ts-ignore
                //console.warn(["fragId no mat!",fragId,fragList.getMaterialId(fragId),this.model.id,this._oldMatKeys.length,Object.values(this.getMatIdMaps())]);
            }
        });
    }
}
class ThemeColorMan {
    InitPromse: Promise<void> = null!;
    constructor(private model: Sippreep.Viewing.Model, private viewer: Sippreep.Viewing.Viewer3D) {
        this.InitPromse = (async () => { })();
    }
    clearColor() {
        this.model.clearThemingColors();
        // this.dbIds.forEach(dbId => {
        //     this.model.visibilityManager.setNodeOff(dbId, false);
        // });
    }
    setColor(dbIds: number[], color: number[]) {
        const temp = new THREE.Vector4(color[0] / 255, color[1] / 255, color[2] / 255, 1);
        dbIds.forEach(dbId => {
            this.model.setThemingColor(dbId, temp, true);
        });
    }
}
/**
 * 节点关闭管理
 *
 * 关闭节点让节点完全不显示
 * @deprecated 由插件viewer.nodeoffhacks完成其功能，此类不再使用。
 */
export class NodeOffMan {
    // private offed = new Set<number>();
    constructor(private model: Sippreep.Viewing.Model, private viewer: Sippreep.Viewing.Viewer3D) {
    }
    clear() {
        // this.offed.forEach(dbId => {
        //     this.model.visibilityManager.setNodeOff(dbId, false);
        // });
        // this.offed.clear();
        try {
            //@ts-ignore
            this.model.visibilityManager.resetNodeOff();
        } catch (error) {
            console.warn("resetNodeOff 异常", error);
        }

    }
    unOff(dbIds: number[]) {
        // dbIds.forEach(dbId => {
        //     if (this.offed.has(dbId)) {
        //         this.model.visibilityManager.setNodeOff(dbId, false);
        //         this.offed.delete(dbId);
        //     }
        // });
        this.model.visibilityManager.setNodeOff(dbIds, false);
    }
    off(dbIds: number[]) {
        // dbIds.forEach(dbId => {
        //     if (!this.offed.has(dbId)) {
        //         this.model.visibilityManager.setNodeOff(dbId, true);
        //         this.offed.add(dbId);
        //     }
        // });
        this.model.visibilityManager.setNodeOff(dbIds, true);
    }
}
class ExternalIdMan {
    private dbIdMap: { [dbId: number]: string } = null!;
    private idMap: { [id: string]: number } = null!;

    InitPromse: Promise<void> = HelperTool.GetNull();
    constructor(private model: Sippreep.Viewing.Model, private viewer: Sippreep.Viewing.Viewer3D) {
        this.InitPromse = this.initPromse();
    }
    private async initPromse() {
        await new Promise((s, f) => {
            this.model.getObjectTree((t) => {
                s(null);
            }, e => {
                f(e)
            });
        });
        await new Promise((s, f) => {
            this.model.getExternalIdMapping(v => {
                let t1: { [dbId: number]: string } = {}
                Object.keys(v).forEach(id => {
                    t1[v[id]] = id;
                });
                this.idMap = v;
                this.dbIdMap = t1;
                s(null);
            }, () => {
                //let temp = this.model.getPropertyDb();
                f("getExternalIdMapping is faill.");
            });
        });
    }
    toExternalId(dbIds: number[]) {
        return dbIds.map(_ => this.dbIdMap[_]);
    }
    toDbId(ids: string[]) {
        return ids.map(_ => this.idMap[_]);
    }
}
class TransformMan {
    private _tree: Sippreep.Viewing.InstanceTree = null!;
    InitPromse: Promise<void> = null!;
    private _fragOldMat = new Map<number, any>();
    constructor(private model: Sippreep.Viewing.Model, private viewer: Sippreep.Viewing.Viewer3D, private service: GroupManService) {
        this.InitPromse = this.initPromse();
    }
    private async initPromse() {
        this._tree = await new Promise<Sippreep.Viewing.InstanceTree>((s, f) => {
            this.model.getObjectTree((tree) => { s(tree) });
        });
    }
    clear() {
        let worldValue = this.getWorldTransforms(this.service.transformDefault());
        this._fragOldMat.forEach((v, k) => this.setTransformForFragId(k, worldValue));
        this._fragOldMat.clear();
    }
    setTransforms(dbIds: number[], tranforms: GroupTransformEntity[]) {
        let fragIds = new Array<number>();
        let tree = this._tree;
        dbIds.forEach((dbId) => {
            tree.enumNodeFragments(dbId, (fragId: number) => {
                fragIds.push(fragId);
            }, true);
        });

        let worldValue = this.getWorldTransforms(...tranforms);

        fragIds.forEach(fragId => {
            this._fragOldMat.set(fragId, null);
            this.setTransformForFragId(fragId, worldValue);
        });
    }
    private setTransformForFragId(fragId: number, worldValue: {
        position: THREE.Vector3,
        quaternion: THREE.Quaternion,
        scale: THREE.Vector3,
    }) {
        //@ts-ignore
        var fragProxy = this.viewer.impl.getFragmentProxy(this.model, fragId);

        fragProxy.getAnimTransform()

        fragProxy.position = worldValue.position;

        fragProxy.scale = worldValue.scale;


        //Not a standard three.js quaternion
        fragProxy.quaternion._x = worldValue.quaternion.x
        fragProxy.quaternion._y = worldValue.quaternion.y
        fragProxy.quaternion._z = worldValue.quaternion.z
        fragProxy.quaternion._w = worldValue.quaternion.w

        fragProxy.updateAnimTransform();
    }
    private getWorldTransforms(...tranforms: GroupTransformEntity[]) {
        let worldPoint = new THREE.Object3D();
        let currentPoint = worldPoint;
        let map = tranforms.map(tranform => {
            let movePoint = new THREE.Object3D();
            movePoint.position.fromArray(tranform.center);
            movePoint.updateMatrixWorld(true);

            currentPoint.position.copy(movePoint.worldToLocal(currentPoint.position));
            currentPoint.parent = movePoint;
            currentPoint = movePoint;

            return { tranform, movePoint };
        });
        const v3 = new THREE.Vector3();
        map.forEach(({ tranform, movePoint }) => {

            movePoint.position.add(v3.fromArray(tranform.position));

            if (tranform.axisAngle) {
                // 当rotation数据长度为4时，取前三个值为旋转轴，第四个值为右手旋转角度数，（中心依旧为center）
                movePoint.rotation.setFromQuaternion(
                    new THREE.Quaternion().setFromAxisAngle(
                        v3.fromArray(tranform.axisAngle.slice(0, 3)).normalize(),
                        tranform.axisAngle.at(-1)! / 180 * Math.PI,
                    ),
                );
            }

            if (tranform.rotation) {
                v3.fromArray(tranform.rotation.map(_ => _ / 180 * Math.PI));
                movePoint.rotation.toVector3().add(v3);
                movePoint.rotation.setFromVector3(movePoint.rotation.toVector3().add(v3));
            }

            movePoint.scale.fromArray(tranform.scale);
        });
        map.reverse().forEach(({ movePoint }) => {
            movePoint.updateMatrixWorld(true);
        });

        return {
            position: worldPoint.getWorldPosition(),
            quaternion: worldPoint.getWorldQuaternion(),
            scale: worldPoint.getWorldScale()
        }
    }
}